﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using ParserGenerator.Collections;
using ParserGenerator.Exceptions;
using ParserGenerator.Extensions;
using ParserGenerator.SyntacticAnalysis;

namespace ParserGenerator.ScannerConstruction
{
    [Serializable]
    public sealed class ScannerAutomatonStartTable : ReadOnlyDictionary<string, ScannerAutomatonStart>
    {
        internal ScannerAutomatonStartTable(GrammarDeclaration grammar)
            : base(new Dictionary<string, ScannerAutomatonStart>())
        {
            if (grammar == null)
                throw new ArgumentNullException(nameof(grammar));
            Identifier defaultStart = Identifier.New(grammar.DefaultScannerStartName);
            StartDeclaration defaultStartDecl = StartDeclaration.New(false, Sequence.Create(defaultStart));
            Define(defaultStartDecl);
            foreach (Directive d in grammar.Directives.Items)
                if (d.IsStart)
                    Define(d.AsStart.Declaration);
        }

        private void Define(StartDeclaration n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            foreach (Identifier item in n.Items)
            {
                ScannerAutomatonStart desc;
                if (Dictionary.TryGetValue(item.Name, out desc))
                {
                    string message = string.Format(SR.StartDefined, item.Name.AsSingleQuote());
                    NameErrorException e = new NameErrorException(message);
                    e.AddLocation(desc.Context);
                    e.AddLocation(item.Context);
                    throw e;
                }
                desc = new ScannerAutomatonStart();
                desc.Number = Dictionary.Count;
                desc.Name = item.Name;
                desc.Context = item.Context;
                Dictionary.Add(item.Name, desc);
                desc.IsExclusive = n.IsExclusive;
            }
        }

        public ScannerAutomatonStart Lookup(Identifier name)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            ScannerAutomatonStart desc;
            if (!Dictionary.TryGetValue(name.Name, out desc))
            {
                string message = string.Format(SR.StartNotDefined, name.Name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(name.Context);
                throw e;
            }
            return desc;
        }

        public HashSet<ScannerAutomatonStart> Resolve(LexicalStartReferenceSet set)
        {
            if (set == null)
                throw new ArgumentNullException(nameof(set));
            HashSet<ScannerAutomatonStart> starts = new HashSet<ScannerAutomatonStart>();
            foreach (Identifier name in set.Names)
            {
                ScannerAutomatonStart start = Lookup(name);
                starts.Add(start);
            }
            if (set.IncludesNonExclusive)
                foreach (var t in Dictionary)
                    if (!t.Value.IsExclusive)
                        starts.Add(t.Value);
            if (set.IncludesAll)
                starts.UnionWith(Dictionary.Values);
            return starts;
        }

        internal Dictionary<ScannerAutomatonState, HashSet<string>> GetGroups()
        {
            var dict = new Dictionary<ScannerAutomatonState, HashSet<string>>();
            foreach (ScannerAutomatonStart start in Dictionary.Values)
            {
                HashSet<string> group;
                if (!dict.TryGetValue(start.State, out group))
                {
                    group = new HashSet<string>();
                    dict.Add(start.State, group);
                }
                group.Add(start.Name);
                if (!dict.TryGetValue(start.StrictState, out group))
                {
                    group = new HashSet<string>();
                    dict.Add(start.StrictState, group);
                }
                group.Add(start.Name + "^");
            }
            return dict;
        }

        public sealed override string ToString()
        {
            return string.Join("\n", Dictionary.Values);
        }
    }
}
